package redis

import (
	"context"
	"errors"
	"fmt"
	"time"

	"gitee.com/youkelike/session"
	redis "github.com/redis/go-redis/v9"
)

var (
	errSessionNotFound = errors.New("session: 找不到 session")
)

// redis 中不能存储对象，所以在 redis 中用 hash 字典来存储 id-key-value
// store 和 session 对象都是 redis 客户端的代理，是一个壳，实际上不存储 id-key-value 数据，
// 每次获取的 session 对象都是临时构造出来的
type Store struct {
	prefix string
	expire time.Duration
	client redis.Cmdable
}

func NewStore(client redis.Cmdable, opts ...StoreOption) *Store {
	s := &Store{
		prefix: "sess",
		expire: time.Minute * 15,
		client: client,
	}

	for _, o := range opts {
		o(s)
	}

	return s
}

type StoreOption func(*Store)

func WithPrefix(prefix string) StoreOption {
	return func(s *Store) {
		s.prefix = prefix
	}
}

func WithExpire(expire time.Duration) StoreOption {
	return func(s *Store) {
		s.expire = expire
	}
}

func (s *Store) Generate(ctx context.Context, id string) (session.Session, error) {
	key := redisKey(s.prefix, id)

	_, err := s.client.HSet(ctx, key, id, id).Result()
	if err != nil {
		return nil, err
	}
	_, err = s.client.Expire(ctx, key, s.expire).Result()
	if err != nil {
		return nil, err
	}

	return &Session{
		id:     id,
		key:    key,
		client: s.client,
	}, nil
}
func (s *Store) Get(ctx context.Context, id string) (session.Session, error) {
	key := redisKey(s.prefix, id)
	cnt, err := s.client.Exists(ctx, key).Result()
	if err != nil {
		return nil, err
	}
	if cnt != 1 {
		return nil, errSessionNotFound
	}

	return &Session{
		id:     id,
		key:    key,
		client: s.client,
	}, nil
}
func (s *Store) Refresh(ctx context.Context, id string) error {
	key := redisKey(s.prefix, id)
	ok, err := s.client.Expire(ctx, key, s.expire).Result()
	if err != nil {
		return err
	}
	if !ok {
		return errSessionNotFound
	}

	return nil
}
func (s *Store) Remove(ctx context.Context, id string) error {
	key := redisKey(s.prefix, id)
	_, err := s.client.Del(ctx, key).Result()
	return err
}

type Session struct {
	// 原始的
	id string
	// 加了前缀的
	key    string
	client redis.Cmdable
}

func (s *Session) Get(ctx context.Context, key string) (any, error) {
	val, err := s.client.HGet(ctx, s.key, key).Result()
	return val, err
}
func (s *Session) Set(ctx context.Context, key string, val any) error {
	const lua = `
if redis.call("exists", KEYS[1])
then
	return redis.call("hset", KEYS[1], ARGV[1], ARGV[2])
else
	return -1
end	
`
	cnt, err := s.client.Eval(ctx, lua, []string{s.key}, key, val).Int()
	if err != nil {
		return err
	}
	if cnt < 0 {
		return errSessionNotFound
	}
	return nil
}
func (s *Session) ID() string {
	return s.id
}

func redisKey(prefix, id string) string {
	return fmt.Sprintf("%s-%s", prefix, id)
}
